<?php
	/**
	  * @package	MyTools
	  * @name		Corrector
	  * @brief		Clase para corregir palabras desde un diccionario
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://opensource.org/licenses/gpl-2.0.php
	  * @date		2010-01-04
	  * @version	0.1.0
  	**/

	final class Corrector extends MyDebuger implements MyVersions {
		/* version de la clase */
		private static $version			= 0;
		/* subversion de la clase */
		private static $subversion		= 1;
		/* numero de revision de la clase */
		private static $revision		= 0;

		/* nombre del archivo con el diccionario */
		private $archivo			= False;
		/* palabra recibida para corregir */
		private $palabra			= False;
		private $palabraOriginal= False;
		/* palabras encontradas en el diccionario */
		private $palabras			= Array();
		/* combinaciones posibles de la palabra */
		private $combinaciones	= Array();
		/* alfabeto */
		private $alfabeto			= 'abcdefghijklmnopqrstuvwxyz';
		/* bandera de corregida */
		private $corregida		= False;

		public function __construct($debug = False) {
			/* creamos el padre */
			parent::__construct($debug, __CLASS__);
			/* organizamos el alfabeto */
			$this->alfabeto = str_split($this->getAlfabeto());
		}

		public function __destruct() {
			/* mostramos un mensaje */
			$this->show('Vaciando diccionario..', False);
			/* mostramos OK */
			$this->showOK();
			/* matamos el padre */
			parent::__destruct();
		}

		public static final function getVersion() {
			/* retornamos la version actual de la clase */
			return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
		}

		public function corregir($palabra = False) {
			try {
				try {
					/* verificamos si no se envio un valor vacio */
					if ($palabra === False)
						/* mostramos un mensaje de error */
						$this->newException('No se espexifico ninguna palabla para corregir');
					/* almacenamos la palabra a corregir */
					$this->palabra = $palabra;
					/* corregimos la palabra */
					$this->corregirPalabra();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public function getCorregida() {
			try {
				try {
					/* verificamos si se corrigio */
					if (!$this->corregida)
						/* salimos con un error */
						$this->newException('No se corrigio ninguna palabra');
					/* devolvemos la palabra */
					return $this->palabra;
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public function getPosibilidades() {
			try {
				try {
					/* verificamos si se corrigio */
					if (!$this->corregida)
						/* salimos con un error */
						$this->newException('No se corrigio ninguna palabra');
					/* devolvemos el array de posibilidades */
					return $this->combinaciones;
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public function cargarDiccionario($file = False) {
			try {
				try {
					/* mostramos un mensaje para saber que hacemos */
					$this->show('Cargando Diccionario..', False);
					/* verificamos si se selecciono un fichero */
					if ($file === False)
						/* mostramos un mensaje de error */
						$this->newException('No se especifico el nombre del archivo');
					$this->show('');
					/* almacenamos el nombre del fichero */
					$this->archivo = $file;
					/* cargamos el diccionario */
					$this->obtenerDatos();
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public function txttohtml($cadena) {
			/* matriz de busqueda */
			$esto		= Array('ñ', 'á', 'é', 'í', 'ó', 'ú');
			/* matriz de reemplazo */
			$porEsto	= Array('&ntilde;', '&aacute;', '&eacute;', '&iacute;', '&oacute;', '&uacute;');
			/* retornamos la cadena convertida */
			return str_replace($esto, $porEsto, $cadena);
		}

		public function cleanDic($nombre = False) {
			try {
				try {
					/* abrimos el archivo */
					$this->leerDic($nombre);
					/* obtenemos las palabras del diccionario */
					$this->getPalabrasDic();
					/* guardamos el nuevo diccionario limpio */
					$this->savePalabrasDic($nombre);
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public function probar($correctas = 25, $cantidad = 2000, $tabla = True) {
			/* probamos que tal funciona */
			if ($tabla)
				echo '<TABLE>';
			/* realizamos un bucle */
			for ($i = 1, $j = 1; $i <= $correctas;) {
				/* armamos una palabra aleatoria */
				$palabra = $this->randString();
				/* corregimos la palabra */
				$this->corregir($palabra);
				/* verificamos si hubo algun cambio */
				if ($palabra != $this->palabra && $tabla) {
					/* mostramos la palabra original */
					echo '<TR><TD>' . $i . ' [' . $j . ']:</TD><TD>' . $this->txttohtml($palabra) . '</TD><TD>=</TD>';
 					/* mostramos las posibilidades obtenidas */
					echo '<TD>';
 					/* recorremos el array de posibilidades */
 					foreach ($this->getPosibilidades() AS $posibilidad)
						echo $this->txttohtml($posibilidad[0]) . ' ';
					echo '</TD><TD>=></TD>';
 					/* mostramos la palabra corregida */
					echo '<TD>' . $this->txttohtml($this->palabra) . '</TD></TR>';
					/* aumentamos la cantidad */
					$i++;
				}
				$j++;
				/* vaciamos el buffer */
				flush();
				/* verificamos si llegamos al total */
				if ($j == $cantidad)
					break;
			}
			/* mostramos el tiempo transcurrido */
			if ($tabla) {
				echo '<TR><TD colspan=\'4\'>Time: ' . $this->getTimeNow() . ' seg.</TD></TR>';
				echo '</TABLE>';
			}
		}

		private function leerDic($nombre) {
			/* realizamos algunas validaciones */
			$this->validarDic($nombre);
			/* abrimos el fichero */
			$this->openDic($nombre);
			/* obtenemos las lineas del archivo */
			$this->getDicLines();
			/* cerramos el fichero */
			$this->closeDic();
			/* obtenemos las palabras del diccionario */
			$this->getPalabrasDic();
			/* guardamos las palabras nuevas */
			$this->savePalabrasDic($nombre);
		}

		private function validarDic($nombre) {
			/* verificamos que ingreso un nombre de archivo */
			if ($nombre === False)
				$this->newException('No se especifico el nombre del fichero');
			/* verificamos que el fichero exista */
			if (!file_exists($nombre))
				$this->newException('El fichero seleccionado no existe');
			/* verificamos que se pueda leer */
			if (!is_readable($nombre))
				$this->newException('No se poseen permisos para leer el fichero');
			/* verificamos que se pueda escribir */
			if (!is_writable($nombre))
				$this->newException('No se poseen permisos para modificar el fichero');
		}

		private function openDic($nombre) {
			/* abrimos el fichero */
			$this->dic = fopen($nombre, 'r');
		}

		private function closeDic() {
			/* cerramos el diccionario */
			fclose($this->dic);
		}

		private function getDicLines() {
			/* creamos un array que contendra las lineas de diccionario */
			$this->lineasDic = Array();
			/* leemos las lineas */
			while (!feof($this->dic))
				/* obtenemos una linea del archivo */
				$this->lineasDic[] = fgets($this->dic);
		}

		private function getPalabrasDic() {
			/* creamos un array temporal */
			$this->palabrasNuevas = Array();
			/* recorremos las lineas del diccionario */
			foreach ($this->lineasDic AS $linea) {
				/* eliminamos los espacios en blanco y pasamos a minusculas */
				$linea = strtolower(trim($linea));
				/* eliminamos los caracteres de palabras */
				$tmp = preg_replace('/[a-záéíóúñ]/i', '', $linea);
				/* recorremos los caracteres que sobraron */
				foreach (str_split($tmp) AS $caracter) {
					/* si el caracter no es un espacio */
					if ($caracter != ' ')
						/* reemplazamos el caracter actual por un espacio */
						$linea = str_replace($caracter, ' ', $linea);
				}
				/* mientras existan dobles espacios */
				while (strstr($linea, '  '))
					/* eliminamos el espacio de mas */
					$linea = str_replace('  ', ' ', $linea);
				/* mientras que la linea finalize en espacio */
				while (substr($linea, -1) == ' ')
					/* eliminamos el ultimo caracter */
					$linea = substr($linea, 0, -1);
				/* mientras que la linea empieze con espacio */
				while (substr($linea, 0, 1) == ' ')
					/* eliminamos el primer caracter */
					$linea = substr($linea, 1);
				/* si la linea no esta vacia */
				if ($linea != '') {
					/* separamos las palabras */
					preg_match_all("/[a-záéíóúñ]{2,}+/i", $linea, $palabras);
					/* recorremos las palabras */
					foreach ($palabras AS $word) {
						/* recorremos el subarray */
						foreach ($word AS $palabra)
							/* agregamos la palabra al array temporal */
							$this->palabrasNuevas[] = $palabra;
					}
				}
			}
			/* desordenamos las palabras */
			shuffle($this->palabrasNuevas);
		}

		private function savePalabrasDic($nombre) {
			/* abrimos el diccionario */
			$this->openNewDic($nombre);
			/* guardamos las palabras */
			$this->saveNewDic();
			/* cerramos el diccionario */
			$this->closeNewDic($nombre);
		}

		private function openNewDic($nombre) {
			/* eliminamos el diccionario viejo */
			unlink($nombre);
			/* abrimos el diccionario */
			$this->dic = fopen($nombre, 'x');
		}

		private function saveNewDic() {
			/* guardamos todas las palabras nuevas */
			fwrite($this->dic, implode(' ', $this->palabrasNuevas));
		}

		private function closeNewDic($nombre) {
			/* cerramos el fichero */
			fclose($this->dic);
			/* asignamos permisos completos */
			chmod($nombre, 0777);
		}

		private function obtenerDatos() {
			/* mostramos un mensaje para saber que hacemos */
			$this->show('Obteniendo datos del texto..', False);
			/* verificamos que exista el texto */
			if (!file_exists($this->archivo))
				/* si no existe, guardamos un log y salimos con un error */
				$this->newException('El fichero con el texto no existe');

			/* verificamos si podemos leer el archivo */
			if (!is_readable($this->archivo))
				/* si no podemos leer, guardamos un log y salimos con un error */
				$this->newException('El fichero con el texto no se puede leer');

			/* mostramos Ok al mensaje */
			$this->showOK();
			/* obtenemos los datos del fichero */
			$this->obtenerPalabras();
			/* no se que hacemos aca.. */
			$this->ordenarIndices();
		}

		private function obtenerPalabras() {
			/* mostramos un mensaje para saber que hacemos y guardamos un log */
			$this->show('Obteniendo palabras desde el texto..', False);
			/* abrimos el fichero con el texto */
			$texto = file_get_contents($this->archivo);
			/* obtenemos solo las palabras */
			preg_match_all("/[a-záéíóúñ]{2,}+/i", $texto, $palabras);
			/* almacenamos las palabras */
			$this->palabras = $palabras[0];
			/* mostramos OK al mensaje */
			$this->showOK();
			/* mostramos la cantidad de palabras */
			$this->show('Palabras obtenidas ' . number_format(count($this->palabras), 0, ',', '.'), False); $this->show('');
		}

		private function ordenarIndices() {
			/* mostramos un mensaje para saber que hacemos */
			$this->show('Ordenando indices..', False);
			/* creamos un nuevo array temporal */
			$nuevas = Array();
			/* recorremos las palabras */
			foreach ($this->palabras AS $indice => $valor) {
				/* creamos una variable temporal para no calcularla 3 veces */
				$strIndice = $this->clean($valor);
				/* almacenamos el indice y aumentamos la cantidad de apariciones */
				if (isset($nuevas[$strIndice]))
					$nuevas[$strIndice][1]++;
				else {
					$nuevas[$strIndice][0] = $valor;
					$nuevas[$strIndice][1] = 1;
				}
			}
			/* almacenamos las palabras del nuevo array */
			$this->palabras = $nuevas;
			/* mostramos OK al mensaje */
			$this->showOK();
			/* almacenamos un log */
			$this->show('Palabras depuradas ' . number_format(count($this->palabras), 0, ',', '.'), False); $this->show('');
		}

		private function clean($string, $side = True) {
			/* para los indices eliminamos los caracteres raros */
			$esto		= Array('á', 'é', 'í', 'ó', 'ú', 'ñ');
			/* por caracteres standard */
			$porEsto	= Array('a', 'e', 'i', 'o', 'u', 'n');
			/* retornamos la cadena modificada */
			if ($side)
				return str_replace($esto, $porEsto, $string);
			else
				return str_replace($porEsto, $esto, $string);
		}

		private function corregirPalabra() {
			/* bajamos la bandera de correccion */
			$this->habilitarBandera(False);

			/* mostramos un mensaje de lo que hacemos */
			$this->show('Corrigiendo palabra \''. $this->txttohtml($this->palabra) .'\'..', False);

			/* limpiamos la palabra */
			$this->limpiarPalabra();
			/* realizamos algunas verificaciones */
			$this->verificaciones();
			/* corregimos la palabra */
			$this->empezarCorregir();

			/* mostramos OK al mensaje */
			$this->show('OK');
			$this->show('Palabra corregida: \''. $this->txttohtml($this->palabra) .'\'', False); $this->show('');
		}

		private function limpiarPalabra() {
			/* limpiamos los espacios */
			$this->palabra = trim($this->palabra);
			/* almacenamos la palabra original */
			$this->palabraOriginal = $this->palabra;
			/* reemplazamos los caracteres extranos */
			$this->palabra = $this->clean($this->palabra);
		}

		private function verificaciones() {
			/* verificamos que exista un diccionario */
			if (count($this->palabras) == 0)
				/* finalizamos con un error */
				$this->salir('No se cargo ningun diccionario');
			/* verificamos si esta vacia */
			if (empty($this->palabra))
				/* finalizamos con un error */
				$this->salir('La palabra esta vacia');
			/* verificamos que la palabra no contenga estacios */
			if (strstr($this->palabra, ' '))
				/* finalizamos con un error */
				$this->salir('No se puede corregir mas de una palabra por vez');
			/* verificamos si la palabra es una sola letra */
			if (strlen($this->palabra) == 1) {
				/* buscamos posibles abreviaciones */
				$this->buscarAbreviaciones();
				/* finalizamos con un mensaje */
				$this->salir('La palabra es una sola letra');
			}
		}

		private function buscarAbreviaciones() {
			/* creamos un array con las abreviaciones utilizadas a veces con una sola palabra */
			$abreviaciones = Array('c' => 'se', 'd' => 'de', 'l' => 'el', 'm' => 'me', 'n' => 'en', 'q' => 'que', 's' => 'es', 'x' => 'por', 't' => 'te');
			/* verificamos si existe alguna de las abreviaciones */
			if (array_key_exists($this->palabra, $abreviaciones))
				/* reemplazamos la abreviacion por su palabra */
				$this->palabra = $abreviaciones[$this->palabra];
		}

		private function empezarCorregir() {
			/* buscamos su tenemos la palabra tal como esta */
			$this->buscarEnPalabras();
			/* creamos todas las combinaciones posibles */
			$this->crearCombinaciones();
			/* hacemos mas combinaciones */
			$this->armarCombinaciones();
			/* verificamos si alguna combinacion existe */
			$this->buscarEnCombinaciones();
			/* buscamos la palabra mas utilizada */
			$this->buscarMasUtilizada();
			/* OK se corrigo la palabra, cambiamos la bandera */
			$this->habilitarBandera();
		}

		private function buscarEnPalabras() {
			/* verificamos si la palabra esta en el array */
			if (array_key_exists($this->palabra, $this->palabras)) {
				/* si existe almacenamos el valor del array */
				$this->palabra = $this->palabras[$this->palabra][0];
				/* finalizamos con un mensaje ya que la palabra esta bien */
				$this->salir('La palabra esta correcta');
			}
		}

		private function crearCombinaciones() {
			/** vamos a crear todas las combinaciones posibles con la palabra **/
			/* tomamos el tamano de la palabra */
			$tanamo = strlen($this->palabra);

			/* recorremos la palabra eliminando un caracter desde el primero al ultimo */
			for ($i = 0; $i < $tanamo; $i++) {
				/* verificamos si la palabra tiene mas de dos caracteres para eliminar uno */
				if ($tanamo > 2)
					/* eliminamos un caracter */
					$this->combinaciones[(substr($this->palabra, 0, $i) . substr($this->palabra, ($i + 1)))] = False;
				/* recorremos el alfabeto */
				foreach ($this->getAlfabeto() AS $letra) {
					/* agregamos la letra en todas las posiciones posibles de la palabra */
					$this->combinaciones[(substr($this->palabra, 0, $i) . $letra . substr($this->palabra, $i + 1))] = False;
				}
			}

			/* cambiamos el orden de las letras en la palabra en todas las posiciones */
			for ($i = 0; $i < ($tanamo - 1); $i++)
				/* agregamos la palabra con los caracteres inveertidos. Ej: hola = ohla, hloa, hoal */
				$this->combinaciones[(substr($this->palabra, 0, $i) . $this->palabra[$i + 1] . $this->palabra[$i] . substr($this->palabra, ($i + 2)))] = False;

			/* vamos a agregar una letra en todas las pocisiones de la palabra */
			for ($i = 0; $i < ($tanamo + 1); $i++) {
				/* recorremos el alfabeto */
				foreach ($this->getAlfabeto() AS $letra)
					/* agregamos la letra en todas las posiciones de la palabra */
					$this->combinaciones[(substr($this->palabra, 0, $i) . $letra . substr($this->palabra, $i))] = False;
			}
		}

		private function armarCombinaciones() {
			/* una combinacion mas posible puede ser las dobles eles 'LL' por una Y */
			$this->palabra = str_replace('ll', 'y', $this->palabra);
			/* recorremos el alfabeto */
			foreach ($this->getAlfabeto() AS $letra) {
				/* buscamos por caracteres repetidos */
				while (strstr($letra . $letra, $this->palabra))
					/* reemplazamos caracteres repetidos */
					$this->palabra = str_replace($letra . $letra, $letra, $this->palabra);
			}
			/* corremos otravez las combinaciones */
			$this->crearCombinaciones();
		}

		private function buscarEnCombinaciones() {
			/* creamos un array temporal con las combinaciones que se encontraron en las palabras */
			$coincidencias = Array();
			/* recorremos las coincidencias de las combinaciones en el array de palabras */
			foreach (array_diff_key($this->combinaciones, array_diff_key($this->combinaciones, $this->palabras)) AS $indice => $tmp)
				/* almacenamos los valores de la coincidencia */
				$coincidencias[] = $this->palabras[$indice];
			/* sobreescribimos el array con los nuevos valores */
			$this->combinaciones = $coincidencias;
		}

		private function buscarMasUtilizada() {
			/* verificamos si se encontro algo */
			if (count($this->combinaciones) == 0) {
				/* si no hay coincidencias, retornamos la palabra a su estado original */
				$this->palabra = $this->palabraOriginal;
				/* salimos con un mensaje */
				$this->salir('No se encontraron coincidencias');
			}
			/* almacenamos el valor mas utilizado */
			$mayor = 0;
			/* recorremos las coincidencias encontradas */
			foreach ($this->combinaciones AS $coincidencia) {
				/* verificamos si es la mas utilizada */
				if ($coincidencia[1] > $mayor) {
					/* almacenamos la cantidad mayor nueva */
					$mayor = $coincidencia[1];
					/* almacenamos la palabra mas utilizada */
					$this->palabra = $coincidencia[0];
				}
			}
		}

		private function habilitarBandera($habilitar = True) {
			/* levantamos la bandera de correccion */
			$this->corregida = $habilitar;
		}

		private function salir($mensaje, $habilitar = True) {
			/* levantamos la bandera de correccion */
			$this->habilitarBandera($habilitar);
			/* salimos con un mensaje */
			$this->newException($mensaje);
		}

		private function randString($min = 4, $max = 15) {
			/* cargamos dos array, uno con las vocales y otro con consonantes */
			$vocales = array('a', 'e', 'i', 'o', 'u');
			$consonantes = array('b', 'c', 'd', 'f', 'g', 'j', 'l', 'm', 'n', 'ñ', 'p', 'r', 's', 't');
			/* aleatorio para empezar con consonantes o vocales */
			$random = rand(0, 1);
			/* string que almacenara la palabra */
			$cadena = '';
			/* recorremos el tamano aleatorio de la cadena */
			for ($j = 0; $j < rand($min, $max); $j++) {
				/* verificamos si es vocal o consonante */
				switch ($random) {
					case 0:
						/* obtenemos una vocal aleatoria y la agregamos la vocal al string */
						$cadena.= $vocales[rand(0, count($vocales) - 1)];
						/* la siguiente sera una consonante */
						$random = 1;
						break;
					case 1:
						/* obtenemos una vocal aleatoria y la agregamos la consonante al string */
						$cadena.= $consonantes[rand(0, count($consonantes) - 1)];
						/* la siguiente sera una vocal */
						$random = 0;
						break;
				}
			}
			/* retornamos la cadena */
			return $cadena;
		}

		protected function getAlfabeto() {
			/* retornamos el alfabeto */
			return $this->alfabeto;
		}
	}
?>